package panda.query.condition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import panda.metainfo.IndexToken;
import panda.query.struct.Attribute;
import panda.query.struct.Constant;
import panda.query.tree.QueryTreeNode;
import panda.record.Schema;
import panda.syntax.SelectStm;

/**
 * A class that extracts the condition relating to the specific node
 * @author Tian Yuan
 *
 */
public class ConditionExtract {

	public static List<Attribute>[] covertToColumn(Schema l, Schema r, Sentence cond) {
		List<Attribute>[] result = new ArrayList[2];
		result[0] = new ArrayList<Attribute>(cond.clauses.size());
		result[1] = new ArrayList<Attribute>(cond.clauses.size());

		for (Clause c : cond.clauses) {
			Attribute la = ((AttributeName)((Relation)c.terms.get(0)).l).getAttribute();
			Attribute ra = ((AttributeName)((Relation)c.terms.get(0)).r).getAttribute();
			if (l.hasAttribute(la) && r.hasAttribute(ra)) {
				result[0].add(la);
				result[1].add(ra);
			}
			else {
				result[0].add(ra);
				result[1].add(la);
			}
		}

		return result;
	}

	public static Attribute getAttribute(Relation r) {
		if (r.l instanceof AttributeName) return ((AttributeName)r.l).attr;
		if (r.r instanceof AttributeName) return ((AttributeName)r.r).attr;
		return null;
	}

	public static Constant getConstant(Relation r) {
		if (r.l instanceof ConstValue) return ((ConstValue)r.l).value;
		if (r.r instanceof ConstValue) return ((ConstValue)r.r).value;
		return null;
	}

	public static int getOp(Relation r) {
		return r.rel;
	}

	public static Relation getOptSelectIndex(QueryTreeNode n, Sentence cond, Map<String, IndexToken> map, HashSet<Clause> used) {
		Clause minClause = null;
		Relation min = null;
		double minRed = 0;

		for (Clause c : cond.clauses)
			if (c.terms.size() == 1 && c.terms.get(0) instanceof Relation){
				Relation temp = (Relation)c.terms.get(0);

				if (temp.rel == Relation.EQ && temp.l instanceof ConstValue && temp.r instanceof AttributeName && n.getSchema().hasAttribute(((AttributeName) temp.r).attr)
						&& map.containsKey(((AttributeName) temp.r).attr.getAttributeName())
						|| temp.r instanceof ConstValue && temp.l instanceof AttributeName && n.getSchema().hasAttribute(((AttributeName) temp.l).attr)
						&& map.containsKey(((AttributeName) temp.l).attr.getAttributeName())) {
					double tmp = temp.reductionFactor(n);

					if (min == null || tmp < minRed) {
						minRed = tmp;
						min = temp;
						minClause = c;
					}
				}
			}
		used.add(minClause);
		return min;
	}

	public static Attribute[] getFirstJoinIndex(Schema sch, Sentence cond, Map<String, IndexToken> map, HashSet<Clause> used) {
		if (map == null) return null;
		for (Clause c : cond.clauses) {
			Attribute l = ((AttributeName)((Relation)c.terms.get(0)).l).attr;
			Attribute r = ((AttributeName)((Relation)c.terms.get(0)).r).attr;
			if (sch.hasAttribute(l) && map.get(l.getAttributeName()) != null) {
				Attribute[] res = new Attribute[2];

				res[0] = r;
				res[1] = l;
				used.add(c);
				return res;
			}

			if (sch.hasAttribute(r) && map.get(r.getAttributeName()) != null) {
				Attribute[] res = new Attribute[2];

				res[1] = l;
				res[0] = r;
				used.add(c);
				return res;
			}

		}
		return null;
	}

	public static Sentence getJoinCondition(Schema l, Schema r, HashSet<Clause> used, Sentence cond) {
		Sentence s = null;

		for (Clause c : cond.clauses) 
			if (!used.contains(c) && c.terms.size() == 1){
				boolean app = true;

				for (BooleanExpression i : c.terms)
					if (i instanceof Relation) {
						if (!((Relation)i).appliesToJoin(l, r)) app = false;
					}
					else app = false;
					

				if (app) {
					if (s == null) s = new Sentence();
					s.addClause(c);
					used.add(c);
				}
			}

		return s;
	}

	public static Sentence getSelectCondition(Schema sch, HashSet<Clause> used, Sentence cond) {
		Sentence s = null;
		
//		System.out.println("****************");
//		for (Attribute sa : sch.getAllAttributes())
//			System.out.println(sa);
//		System.out.println("****************");
//
//		System.out.println(cond.clauses.size());
		for (Clause c : cond.clauses) 
			if (!used.contains(c)){
				boolean app = true;

//				System.out.println(((AttributeName)((Relation)c.terms.get(0)).l).attr);
				for (BooleanExpression i : c.terms)
					if (i instanceof Relation) {
						if (!((Relation)i).appliesToSelect(sch))
							app = false;
					}
					else
						app = false;

				if (app) {
					if (s == null) s = new Sentence();
					s.addClause(c);
					used.add(c);
//					System.out.println(((AttributeName)((Relation)c.terms.get(0)).l).attr);
				}
			}
//		System.out.println("*****END***********");

		return s;
	}

	//The following two functions extracts subqueries from a condition expression

	/**
	 * 
	 * @param s
	 * @return
	 */
	public static DecomposeResult decompose(Sentence s) {
		Collection<SelectStm> subSet = new ArrayList<SelectStm>();
		Collection<AttributeName> attrSet = new HashSet<AttributeName>();

		for (Clause c : s.clauses) {
			for (BooleanExpression b : c.terms) {
//				if (b instanceof InExpression) {
//					InExpression iv = (InExpression)b;
//					traverseDecompose(iv.expr, subSet, attrSet);
//					subSet.add(iv.stm);
//				}
				if (b instanceof AllExpression) {
					AllExpression av = (AllExpression)b;
					traverseDecompose(av.expr, subSet, attrSet);
					subSet.add(av.stm);
				}
				if (b instanceof AnyExpression) {
					AnyExpression av = (AnyExpression)b;
					traverseDecompose(av.expr, subSet, attrSet);
					subSet.add(av.stm);
				}
				if (b instanceof ExistsExpression) {
					ExistsExpression ev = (ExistsExpression)b;
					subSet.add(ev.stm);
				}
				if (b instanceof Relation) {
					traverseDecompose(((Relation) b).l, subSet, attrSet);
					traverseDecompose(((Relation) b).r, subSet, attrSet);
				}
			}
		}
		DecomposeResult a = new DecomposeResult(subSet, attrSet);
		return a;
	}
	
	public static DecomposeResult decompose(ValueExpression v) {
		Collection<SelectStm> subSet = new ArrayList<SelectStm>();
		Collection<AttributeName> attrSet = new ArrayList<AttributeName>();
		
		traverseDecompose(v, subSet, attrSet);
		
		return new DecomposeResult(subSet, attrSet);
	}

	private static void traverseDecompose(ValueExpression v, Collection<SelectStm> supSet, Collection<AttributeName> attrSet) {
		if (v instanceof OpExpression) {
			traverseDecompose(((OpExpression) v).l, supSet, attrSet);
			traverseDecompose(((OpExpression) v).r, supSet, attrSet);
		}
		if (v instanceof SubqueryValue) {
			supSet.add(((SubqueryValue) v).statement);
		}
		if (v instanceof AttributeName)
			attrSet.add((AttributeName)v);
	}

	//The following methods deal with PROJECT columns
	public static Set<Attribute> getProject(Sentence s, Set<Clause> used) {
		Set<Attribute> res = new HashSet<Attribute>();
		
		if (s != null)
		for (Clause c : s.clauses)
			if (!used.contains(c)){
				for (BooleanExpression b : c.terms) {
//					if (b instanceof InExpression)
//						traverseProject(((InExpression) b).expr, res);
					if (b instanceof AllExpression)
						traverseProject(((AllExpression) b).expr, res);
					if (b instanceof AnyExpression)
						traverseProject(((AnyExpression) b).expr, res);
					if (b instanceof Relation) {
						traverseProject(((Relation) b).l, res);
						traverseProject(((Relation) b).r, res);
					}
				}
			}
		return res;
	}
	
	private static void traverseProject(ValueExpression v, Set<Attribute> attrSet) {
		if (v instanceof OpExpression) {
			traverseProject(((OpExpression) v).l, attrSet);
			traverseProject(((OpExpression) v).r, attrSet);
		}
		if (v instanceof AttributeName)
			attrSet.add(((AttributeName) v).attr);
	}
	
	public static List<Clause> getClauses(Sentence s) {
		return s.clauses;
	}
	
	public static List<BooleanExpression> getBooleanExpressions(Clause c) {
		return c.terms;
	}
	
	//Extract the remaining conditions according to the used set
	
	public static Sentence getRemaining(Sentence cond, Set<Clause> used) {
		
		if (cond == null) return null;
		
		Sentence res = null;
		
		for (Clause c : cond.clauses)
			if (!used.contains(c)) {
				if (res == null) res = new Sentence();
				
				res.addClause(c);
			}
		
		return res;
	}
	
}
